
package com.taotao.job.core.util;

import com.google.common.base.Preconditions;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * 
 * 从spring抠出来的代码
 * 
 * 
 * Static convenience methods for JavaBeans: for instantiating beans, checking
 * bean property types, copying bean properties, etc.
 *
 * <p>
 * Mainly for use within the framework, but to some degree also useful for
 * application classes.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Sam Brannen
 */
public abstract class BeanUtils {

	/**
	 * Find a method with the given method name and the given parameter types,
	 * declared on the given class or one of its superclasses. Prefers public
	 * methods, but will return a protected, package access, or private method too.
	 * <p>
	 * Checks {@code Class.getMethod} first, falling back to
	 * {@code findDeclaredMethod}. This allows to find public methods without issues
	 * even in environments with restricted Java security settings.
	 * 
	 * @param clazz      the class to check
	 * @param methodName the name of the method to find
	 * @param paramTypes the parameter types of the method to find
	 * @return the Method object, or {@code null} if not found
	 * @see Class#getMethod
	 * @see #findDeclaredMethod
	 */
	public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
		try {
			return clazz.getMethod(methodName, paramTypes);
		} catch (NoSuchMethodException ex) {
			return findDeclaredMethod(clazz, methodName, paramTypes);
		}
	}

	/**
	 * Find a method with the given method name and the given parameter types,
	 * declared on the given class or one of its superclasses. Will return a public,
	 * protected, package access, or private method.
	 * <p>
	 * Checks {@code Class.getDeclaredMethod}, cascading upwards to all
	 * superclasses.
	 * 
	 * @param clazz      the class to check
	 * @param methodName the name of the method to find
	 * @param paramTypes the parameter types of the method to find
	 * @return the Method object, or {@code null} if not found
	 * @see Class#getDeclaredMethod
	 */
	public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
		try {
			return clazz.getDeclaredMethod(methodName, paramTypes);
		} catch (NoSuchMethodException ex) {
			if (clazz.getSuperclass() != null) {
				return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
			}
			return null;
		}
	}

	/**
	 * Find a method with the given method name and minimal parameters (best case:
	 * none), declared on the given class or one of its superclasses. Prefers public
	 * methods, but will return a protected, package access, or private method too.
	 * <p>
	 * Checks {@code Class.getMethods} first, falling back to
	 * {@code findDeclaredMethodWithMinimalParameters}. This allows for finding
	 * public methods without issues even in environments with restricted Java
	 * security settings.
	 * 
	 * @param clazz      the class to check
	 * @param methodName the name of the method to find
	 * @return the Method object, or {@code null} if not found
	 * @throws IllegalArgumentException if methods of the given name were found but
	 *                                  could not be resolved to a unique method
	 *                                  with minimal parameters
	 * @see Class#getMethods
	 * @see #findDeclaredMethodWithMinimalParameters
	 */
	public static Method findMethodWithMinimalParameters(Class<?> clazz, String methodName)
			throws IllegalArgumentException {

		Method targetMethod = findMethodWithMinimalParameters(clazz.getMethods(), methodName);
		if (targetMethod == null) {
			targetMethod = findDeclaredMethodWithMinimalParameters(clazz, methodName);
		}
		return targetMethod;
	}

	/**
	 * Find a method with the given method name and minimal parameters (best case:
	 * none), declared on the given class or one of its superclasses. Will return a
	 * public, protected, package access, or private method.
	 * <p>
	 * Checks {@code Class.getDeclaredMethods}, cascading upwards to all
	 * superclasses.
	 * 
	 * @param clazz      the class to check
	 * @param methodName the name of the method to find
	 * @return the Method object, or {@code null} if not found
	 * @throws IllegalArgumentException if methods of the given name were found but
	 *                                  could not be resolved to a unique method
	 *                                  with minimal parameters
	 * @see Class#getDeclaredMethods
	 */
	public static Method findDeclaredMethodWithMinimalParameters(Class<?> clazz, String methodName)
			throws IllegalArgumentException {

		Method targetMethod = findMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName);
		if (targetMethod == null && clazz.getSuperclass() != null) {
			targetMethod = findDeclaredMethodWithMinimalParameters(clazz.getSuperclass(), methodName);
		}
		return targetMethod;
	}

	/**
	 * Find a method with the given method name and minimal parameters (best case:
	 * none) in the given list of methods.
	 * 
	 * @param methods    the methods to check
	 * @param methodName the name of the method to find
	 * @return the Method object, or {@code null} if not found
	 * @throws IllegalArgumentException if methods of the given name were found but
	 *                                  could not be resolved to a unique method
	 *                                  with minimal parameters
	 */
	public static Method findMethodWithMinimalParameters(Method[] methods, String methodName)
			throws IllegalArgumentException {

		Method targetMethod = null;
		int numMethodsFoundWithCurrentMinimumArgs = 0;
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				int numParams = method.getParameterTypes().length;
				if (targetMethod == null || numParams < targetMethod.getParameterTypes().length) {
					targetMethod = method;
					numMethodsFoundWithCurrentMinimumArgs = 1;
				} else {
					if (targetMethod.getParameterTypes().length == numParams) {
						// Additional candidate with same length
						numMethodsFoundWithCurrentMinimumArgs++;
					}
				}
			}
		}
		if (numMethodsFoundWithCurrentMinimumArgs > 1) {
			throw new IllegalArgumentException("Cannot resolve method '" + methodName
					+ "' to a unique method. Attempted to resolve to overloaded method with "
					+ "the least number of parameters, but there were " + numMethodsFoundWithCurrentMinimumArgs
					+ " candidates.");
		}
		return targetMethod;
	}



	/**
	 * Check if the given type represents a "simple" property: a primitive, a String
	 * or other CharSequence, a Number, a Date, a URI, a URL, a Locale, a Class, or
	 * a corresponding array.
	 * <p>
	 * Used to determine properties to check for a "simple" dependency-check.
	 * 
	 * @param clazz the type to check
	 * @return whether the given type represents a "simple" property
	 */
	public static boolean isSimpleProperty(Class<?> clazz) {
		Preconditions.checkArgument(null != clazz, "Class must not be null");
		return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
	}

	/**
	 * Check if the given type represents a "simple" value type: a primitive, a
	 * String or other CharSequence, a Number, a Date, a URI, a URL, a Locale or a
	 * Class.
	 * 
	 * @param clazz the type to check
	 * @return whether the given type represents a "simple" value type
	 */
	public static boolean isSimpleValueType(Class<?> clazz) {
		return ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() || CharSequence.class.isAssignableFrom(clazz)
				|| Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) || clazz.equals(URI.class)
				|| clazz.equals(URL.class) || clazz.equals(Locale.class) || clazz.equals(Class.class);
	}

	/**
	 * Copy the property values of the given source bean into the target bean.
	 * <p>
	 * Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that
	 * the source bean exposes but the target bean does not will silently be
	 * ignored.
	 * <p>
	 * This is just a convenience method. For more complex transfer needs, consider
	 * using a full BeanWrapper.
	 * 
	 * @param source the source bean
	 * @param target the target bean
	 * @throws Exception if the copying failed
	 */
	public static void copyProperties(Object source, Object target) throws Exception {
		copyProperties(source, target, null, (String[]) null);
	}

	/**
	 * Copy the property values of the given source bean into the given target bean,
	 * only setting properties defined in the given "editable" class (or interface).
	 * <p>
	 * Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that
	 * the source bean exposes but the target bean does not will silently be
	 * ignored.
	 * <p>
	 * This is just a convenience method. For more complex transfer needs, consider
	 * using a full BeanWrapper.
	 * 
	 * @param source   the source bean
	 * @param target   the target bean
	 * @param editable the class (or interface) to restrict property setting to
	 * @throws Exception if the copying failed
	 */
	public static void copyProperties(Object source, Object target, Class<?> editable) throws Exception {
		copyProperties(source, target, editable, (String[]) null);
	}

	/**
	 * Copy the property values of the given source bean into the given target bean,
	 * ignoring the given "ignoreProperties".
	 * <p>
	 * Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that
	 * the source bean exposes but the target bean does not will silently be
	 * ignored.
	 * <p>
	 * This is just a convenience method. For more complex transfer needs, consider
	 * using a full BeanWrapper.
	 * 
	 * @param source           the source bean
	 * @param target           the target bean
	 * @param ignoreProperties array of property names to ignore
	 * @throws Exception if the copying failed
	 */
	public static void copyProperties(Object source, Object target, String... ignoreProperties) throws Exception {
		copyProperties(source, target, null, ignoreProperties);
	}

	/**
	 * Copy the property values of the given source bean into the given target bean.
	 * <p>
	 * Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that
	 * the source bean exposes but the target bean does not will silently be
	 * ignored.
	 * 
	 * @param source           the source bean
	 * @param target           the target bean
	 * @param editable         the class (or interface) to restrict property setting
	 *                         to
	 * @param ignoreProperties array of property names to ignore
	 * @throws Exception if the copying failed
	 */
	private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
			throws Exception {

		Preconditions.checkArgument(null != source, "Source must not be null");
		Preconditions.checkArgument(null != target, "Target must not be null");

		Class<?> actualEditable = target.getClass();
		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName()
						+ "] not assignable to Editable class [" + editable.getName() + "]");
			}
			actualEditable = editable;
		}
		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;

		for (PropertyDescriptor targetPd : targetPds) {
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
				if (sourcePd != null) {
					Method readMethod = sourcePd.getReadMethod();
					if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],
							readMethod.getReturnType())) {
						try {
							if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
								readMethod.setAccessible(true);
							}
							Object value = readMethod.invoke(source);
							if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
								writeMethod.setAccessible(true);
							}
							writeMethod.invoke(target, value);
						} catch (Throwable ex) {
							throw new Exception(
									"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
						}
					}
				}
			}
		}
	}
	
	


	

	private static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz)
			throws NoSuchFieldException, SecurityException, IntrospectionException {
		Field[] fields = clazz.getDeclaredFields();
		if (null == fields || 0 == fields.length) {
			return new PropertyDescriptor[] {};
		}

		PropertyDescriptor[] pds = new PropertyDescriptor[fields.length];

		for (int i = 0; i < fields.length; i++) {
			pds[i] = getPropertyDescriptor(clazz, fields[i].getName());
		}

		return pds;
	}

	private static PropertyDescriptor getPropertyDescriptor(Class<? extends Object> clazz, String name)
			throws IntrospectionException, NoSuchFieldException, SecurityException {
		Field filed = clazz.getDeclaredField(name);
		filed.setAccessible(true);
		PropertyDescriptor pd = new PropertyDescriptor(name, clazz);
		return pd;
	}

}
